#include "SqliteDataSource.h"
#include "common/AES.h"
#include <sstream>
#include <vector>

namespace etu {
namespace db {

using etu::common::AES;

SqliteDataSource::SqliteDataSource(const std::string& dbUrl) 
        throw(DataSourceException) {
    
    int res = sqlite3_open(dbUrl.c_str(), &this->connection);

    if(res != 0) {
        std::ostringstream oss;
        oss << "Ошибка при открытии источника данных. ";
        oss << sqlite3_errmsg(this->connection);

        sqlite3_close(this->connection);

        throw DataSourceException(oss.str());
    }

    this->initDb();
}

SqliteDataSource::SqliteDataSource(const SqliteDataSource& orig) : DataSource(orig) {
    this->connection = orig.connection;
}

SqliteDataSource::~SqliteDataSource() {
    if(this->connection != NULL) {
        sqlite3_close(this->connection);
    }
}

std::list<std::string>* SqliteDataSource::getGenealogyTreeNames()
        throw(DataSourceException) {

    std::list<std::string>* ret = new std::list<std::string>();

    std::vector<std::vector<std::string> > data
            = this->executeQuery("SELECT name FROM genealogy_tree");

    for(unsigned int rowIndex = 0; rowIndex < data.size(); rowIndex++) {
        std::vector<std::string> row = data[rowIndex];
        ret->push_back(row[0]);
    }

    return ret;
}

void SqliteDataSource::addGenealogyTree(const std::string& name,
        const GenealogyTree& tree) throw(DataSourceException) {

    if(this->isGenealogyTreeExists(name)) {
        throw DataSourceException("Генеалогическое дерево с заданным именем уже существует");
    }

    std::ostringstream oss;
    oss << tree;
    
    std::ostringstream encodeOss;
    encodeOss << AES(oss.str());

    std::string serializedTree = encodeOss.str();
    const char* query = "INSERT INTO genealogy_tree (name, tree) VALUES (?, ?)";
    sqlite3_stmt *pStmt;
    int rc;

    do {
        rc = sqlite3_prepare(this->connection, query, -1, &pStmt, 0);

        if(rc != SQLITE_OK) {
            std::ostringstream oss;
            oss << sqlite3_errmsg(this->connection);
            throw DataSourceException(oss.str());
        }

        sqlite3_bind_text(pStmt, 1, name.c_str(), -1, SQLITE_STATIC);
        sqlite3_bind_text(pStmt, 2, serializedTree.c_str(), -1, SQLITE_STATIC);

        rc = sqlite3_step(pStmt);
        rc = sqlite3_finalize(pStmt);
    } while(rc == SQLITE_SCHEMA);
}

void SqliteDataSource::deleteGenealogyTree(const std::string& name)
        throw(DataSourceException) {

    std::ostringstream query;
    query << "DELETE FROM genealogy_tree WHERE name = '" << name << "'";

    this->executeUpdate(query.str());
}

void SqliteDataSource::updateGenealogyTree(const std::string& name,
        const GenealogyTree& tree) throw(DataSourceException) {

    if(!this->isGenealogyTreeExists(name)) {
        throw DataSourceException("Генеалогическое дерево с заданным именем не существует");
    }

    this->deleteGenealogyTree(name);
    this->addGenealogyTree(name, tree);
}

std::vector<std::vector<std::string> > SqliteDataSource::executeQuery(const std::string& query)
    throw(DataSourceException) {

    std::vector<std::vector<std::string> > ret;
    sqlite3_stmt *stmt;
    const char* tail;

    int res = sqlite3_prepare(this->connection, query.c_str(), 1000, &stmt, &tail);

    if(res != SQLITE_OK) {
        std::ostringstream oss;
        oss << "Невозможно выполнить операцию. ";
        oss << sqlite3_errmsg(this->connection);
        throw DataSourceException(oss.str());
    }

    std::ostringstream oss;
    std::string text;

    while((res = sqlite3_step(stmt)) != SQLITE_DONE) {
        switch(res) {
            case SQLITE_BUSY:
                sleep(1);
                break;
            case SQLITE_ERROR:
                oss << "Невозможно выполнить операцию. ";
                oss << sqlite3_errmsg(this->connection);
                throw DataSourceException(oss.str());
                break;
            case SQLITE_ROW:
                std::vector<std::string> row;
                int columns = sqlite3_column_count(stmt);

                for(int i = 0; i < columns; i++) {
                    switch(sqlite3_column_type(stmt, i)) {
                        case SQLITE_TEXT:
                            text = (const char*) sqlite3_column_text(stmt, i);
                            row.push_back(text);
                            break;
                        default:
                            // omit
                            break;
                    }
                }

                ret.push_back(row);
                break;
        }
    }
    
    return ret;
}

void SqliteDataSource::executeUpdate(const std::string& query)
    throw(DataSourceException) {
    
    int res = sqlite3_exec(this->connection, query.c_str(), NULL, NULL, NULL);

    if(res != SQLITE_OK) {
        std::ostringstream oss;
        oss << "Невозможно выполнить операцию. ";
        oss << sqlite3_errmsg(this->connection);
        throw DataSourceException(oss.str());
    }
    
    sqlite3_exec(this->connection, "COMMIT;", NULL, NULL, NULL);
}

GenealogyTree* SqliteDataSource::getGenealogyTree(const std::string& name)
        throw(DataSourceException) {

    GenealogyTree* ret = NULL;

    std::vector<std::vector<std::string> > data
            = this->executeQuery("SELECT name, tree FROM genealogy_tree");

    for(unsigned int rowIndex = 0; rowIndex < data.size(); rowIndex++) {
        std::vector<std::string> row = data[rowIndex];

        if(row[0] == name) {
            std::istringstream iss;
            iss.str(AES::decodeString(row[1]));

            ret = new GenealogyTree();

            try {
                iss >> (* ret);
            } catch(const ParseException& e) {
                throw DataSourceException(e.what());
            }
            
            break;
        }
    }

    return ret;
}

bool SqliteDataSource::isGenealogyTreeExists(const std::string& name)
        throw(DataSourceException) {

    sqlite3_stmt *stmt;
    const char* tail;

    int res = sqlite3_prepare(this->connection,
            "SELECT name from genealogy_tree", 1000, &stmt, &tail);

    if(res != SQLITE_OK) {
        std::ostringstream oss;
        oss << "Невозможно выполнить операцию. ";
        oss << sqlite3_errmsg(this->connection);
        throw DataSourceException(oss.str());
    }

    std::ostringstream oss;
    std::string tableName;

    while((res = sqlite3_step(stmt)) != SQLITE_DONE) {
        switch(res) {
            case SQLITE_BUSY:
                sleep(1);
                break;
            case SQLITE_ERROR:
                oss << "Невозможно выполнить операцию. ";
                oss << sqlite3_errmsg(this->connection);
                throw DataSourceException(oss.str());
                break;
            case SQLITE_ROW:
                tableName = (const char*) sqlite3_column_text(stmt, 0);

                if(tableName == name) {
                    return true;
                }
                break;
        }
    }

    return false;
}

void SqliteDataSource::initDb() throw(DataSourceException) {
    this->executeUpdate("CREATE TABLE IF NOT EXISTS genealogy_tree "
        "(name VARCHAR, tree VARCHAR)");
    this->executeUpdate("CREATE TABLE IF NOT EXISTS photo "
        "(hash VARCHAR, photo_data VARCHAR)");
}

bool SqliteDataSource::isPhotoExists(const Person& person) throw(DataSourceException) {
    sqlite3_stmt *stmt;
    const char* tail;
    const std::string photoId = person.getPhotoId();

    if(photoId.empty()) {
        return false;
    }

    int res = sqlite3_prepare(this->connection,
            "SELECT hash from photo", 1000, &stmt, &tail);

    if(res != SQLITE_OK) {
        std::ostringstream oss;
        oss << "Невозможно выполнить операцию. ";
        oss << sqlite3_errmsg(this->connection);
        throw DataSourceException(oss.str());
    }

    std::ostringstream oss;
    std::string photoHash;

    while((res = sqlite3_step(stmt)) != SQLITE_DONE) {
        switch(res) {
            case SQLITE_BUSY:
                sleep(1);
                break;
            case SQLITE_ERROR:
                oss << "Невозможно выполнить операцию. ";
                oss << sqlite3_errmsg(this->connection);
                throw DataSourceException(oss.str());
                break;
            case SQLITE_ROW:
                photoHash = (const char*) sqlite3_column_text(stmt, 0);

                if(photoHash == photoId) {
                    return true;
                }
                break;
        }
    }

    return false;
}

Photo* SqliteDataSource::getPhoto(const Person& person) throw(DataSourceException) {
    const std::string photoId = person.getPhotoId();

    if(photoId.empty()) {
        return Photo::empty();
    }

    sqlite3_stmt *pStmt;
    const char* tail;
    
    int rc = sqlite3_prepare(this->connection,
            "SELECT photo_data FROM photo WHERE hash = ?", 1000, &pStmt, &tail);

    sqlite3_bind_text(pStmt, 1, photoId.c_str(), -1, SQLITE_STATIC);

    if(rc != SQLITE_OK) {
        std::ostringstream oss;
        oss << "Невозможно выполнить операцию. ";
        oss << sqlite3_errmsg(this->connection);
        throw DataSourceException(oss.str());
    }

    std::ostringstream oss;
    std::string text;

    while((rc = sqlite3_step(pStmt)) != SQLITE_DONE) {
        switch(rc) {
            case SQLITE_BUSY:
                sleep(1);
                break;
            case SQLITE_ERROR:
                oss << "Невозможно выполнить операцию. ";
                oss << sqlite3_errmsg(this->connection);
                throw DataSourceException(oss.str());
                break;
            case SQLITE_ROW:
                switch(sqlite3_column_type(pStmt, 0)) {
                    case SQLITE_TEXT:
                        text = (const char*) sqlite3_column_text(pStmt, 0);
                        break;
                    default:
                        // omit
                        break;
                }

                break;
        }
    }

    std::string decoded = AES::decodeString(text);

    return new Photo(photoId, (unsigned char *) decoded.c_str(), (int) decoded.size());
}

void SqliteDataSource::addPhoto(Person* person, const Photo& photo) throw(DataSourceException) {
    person->setPhotoId(photo.getHash());

    if(this->isPhotoExists(* person)) {
        this->deletePhoto(* person);
    }

    std::string encoded = AES::encode(photo.getData(), photo.getDataLength());
    const char* query = "INSERT INTO photo (hash, photo_data) VALUES (?, ?)";
    sqlite3_stmt *pStmt;
    int rc;

    do {
        rc = sqlite3_prepare(this->connection, query, -1, &pStmt, 0);

        if(rc != SQLITE_OK) {
            std::ostringstream oss;
            oss << sqlite3_errmsg(this->connection);
            throw DataSourceException(oss.str());
        }

        sqlite3_bind_text(pStmt, 1, photo.getHash().c_str(), -1, SQLITE_STATIC);
        sqlite3_bind_text(pStmt, 2, encoded.c_str(), -1, SQLITE_STATIC);

        rc = sqlite3_step(pStmt);

        if(rc == SQLITE_ROW) {
            std::ostringstream oss;
            oss << sqlite3_errmsg(this->connection);
            throw DataSourceException(oss.str());
        }

        rc = sqlite3_finalize(pStmt);
    } while(rc == SQLITE_SCHEMA);
}

void SqliteDataSource::deletePhoto(const Person& person) throw(DataSourceException) {
    std::string photoId = person.getPhotoId();
    
    if(!photoId.empty()) {
        std::ostringstream query;
        query << "DELETE FROM photo WHERE hash = '" << photoId << "'";

        this->executeUpdate(query.str());
    }
}

}
}
